ALBのアクセスログにアベイアビリティゾーン情報を追加してみた
はじめに
AWSチームのすずきです。
ELB (Application Load Balancer:以下ALB) アクセスログは、接続元となるクライアント(ブラウザ等)と、接続先のターゲット(httpd等が稼働するEC2)のIPアドレスを確認することが可能です。
今回、アクセスログに含まれない ALBノードのIPアドレス、アベイアビリティゾーン単位での解析を実施する必要が生じたため、 アクセスログのファイル名よりALBノードのIPアドレス情報を取得し、ネットワークインターフェイス情報からアベイアビリティゾーンを求める機会がありましたので紹介します。
アクセスログ仕様
ALBのアクセスログの仕様は、2019年9月時点の公式ドキュメントを参考にしました。
ログファイル
ログファイル名に含まれる「ip-address」より、ALBノードのIPアドレスを求めます。
フィールド | 説明 |
---|---|
bucket | S3 バケットの名前。 |
プレフィックス | バケットのプレフィックス (論理階層) |
aws-account-id | 所有者の AWS アカウント ID。 |
リージョン | ロードバランサーおよび S3 バケットのリージョン。 |
yyyy/mm/dd | ログが配信された日付。 |
load-balancer-id | ロードバランサーのリソース ID |
end-time | ログ作成の間隔が終了した日時 |
ip-address | リクエストを処理したロードバランサーノードの IP アドレス。内部ロードバランサーの場合、プライベート IP アドレスです。 |
random-string | システムによって生成されたランダム文字列。 |
ログファイル名サンプル
bucket[/prefix]/AWSLogs/aws-account-id/elasticloadbalancing/region/yyyy/mm/dd/aws-account-id_elasticloadbalancing_region_load-balancer-id_end-time_ip-address_random-string.log.gz
アクセスログファイル
フィールド | 説明 |
---|---|
type | リクエストまたは接続のタイプ。 |
timestamp | ロードバランサーがクライアントに対してレスポンスを生成した時刻 |
elb | ロードバランサーのリソース ID。 |
client:port | リクエストを送信したクライアントの IP アドレスとポート。 |
target:port | このリクエストを処理したターゲットの IP アドレスとポート。 |
request_processing_time | ロードバランサーがリクエストを受け取った時点からターゲットに送信するまでの合計経過時間 |
target_processing_time | ロードバランサーがターゲットにリクエストを送信した時点から、そのターゲットが応答ヘッダーの送信を開始した時点までの合計経過時間 |
response_processing_time | ロードバランサーがターゲットから応答ヘッダーを受け取った時点から、クライアントへの応答の送信を開始した時点までの合計経過時間 |
elb_status_code | ロードバランサーからの応答のステータスコード。 |
target_status_code | ターゲットから応答のステータスコード。 |
received_bytes | クライアント (リクエスタ) から受け取ったリクエストのサイズ |
sent_bytes | クライアント (リクエスタ) に返される応答のサイズ |
request | クライアントからのリクエスト |
user_agent | リクエスト元のクライアントを特定する User-Agent 文字列 |
ssl_cipher | SSL 暗号 |
ssl_protocol | SSL プロトコル |
target_group_arn | ターゲットグループの Amazon リソースネーム |
trace_id | X-Amzn-Trace-Id ヘッダーのコンテンツ |
domain_name | TLS ハンドシェイク中にクライアントから提供される SNI ドメイン |
chosen_cert_arn | クライアントに提示される証明書の ARN |
matched_rule_priority | リクエストに一致したルールの優先度の値 |
request_creation_time | ロードバランサーがクライアントからリクエストを受け取った時刻 |
actions_executed | リクエストの処理時に実行されるアクション |
redirect_url | HTTP レスポンスのロケーションヘッダーのリダイレクトターゲットの URL |
error_reason | エラー理由コード |
CLI
CLIを利用して、ALBノードのIPアドレスがアクセスログファイル名に含まれる事、 ネットワークインターフェイス情報より、アベイアビリティゾーン、プライベートIP情報が取得出来る事を確認しました。
hosts
- ALBのDNS名より、正引きのIPアドレスを確認します。
$ host public-xx.ap-northeast-1.elb.amazonaws.com public-xxxx.ap-northeast-1.elb.amazonaws.com has address 13.113.xx.63 public-xxxx.ap-northeast-1.elb.amazonaws.com has address 54.199.xx.110
アクセスログファイル
- ALBのログ出力先のS3、ログファイル名にALBのIPアドレスが含まれる事を確認します。
$ aws s3 ls s3://aa/AWSLogs/xx/elasticloadbalancing/ap-northeast-1/2019/09/07/ | awk '{print $4}' xx_elasticloadbalancing_ap-northeast-1_app.pub.yy_20190907T0745Z_13.113.xx.63_zzz.log.gz xx_elasticloadbalancing_ap-northeast-1_app.pub.yy_20190907T0745Z_54.199.xx.110_zzz.log.gz
ノードのIPアドレス
- アクセスログのキー(ファイル名)を「_」区切りの配列とし、末尾から2つ目の要素からALBノードのIPアドレスが抽出出来る事を確認します。
$ aws s3 ls s3://aa/pub/AWSLogs/xxx/elasticloadbalancing/ap-northeast-1/2019/09/07/ | awk '{print $4}' | awk -F '_' '{print $(NF-1)}' 13.113.xx.63 54.199.xx.110
ノードのアベイアビリティゾーン
- EC2のAPI「describe-network-interfaces」を利用し、ALBノードのIPアドレスに一致するネットワークインターフェスの情報を確認します。
$ aws ec2 describe-network-interfaces --filters 'Name=addresses.association.public-ip,Values=13.113.xx.63' --query 'NetworkInterfaces[*].[NetworkInterfaceId,AvailabilityZone,PrivateIpAddress,Association.PublicIp]' [ [ "eni-xxx", "ap-northeast-1a", "172.31.xx.223", "13.113.xx.63" ] ]
- 「Internal」のALBの場合、フィルタの対象を「private-ip-address」とします。
AvailabilityZone
$ aws ec2 describe-network-interfaces --filters 'Name=addresses.private-ip-address,Values=172.31.xx.29' --query 'NetworkInterfaces[*].[NetworkInterfaceId,AvailabilityZone,PrivateIpAddress]' [ [ "eni-xxx", "ap-northeast-1a", "172.31.xx.29" ] ]
Python(Boto3)
Lambda (Python) で実装する前提で、Boto3を利用してアベイアビリティゾーンを求めてみました。
ec2 = boto3.client('ec2') s3key='prefix/AWSLogs/xx_elasticloadbalancing_ap-northeast-1_app.pub.yy_20190907T0745Z_13.113.xx.63_zzz.log.gz' # elb ip elb_listener_ip = s3key.split('_')[-2] # public elb responce = ec2.describe_network_interfaces( Filters=[{'Name':'association.public-ip','Values':[elb_listener_ip]}] ) # internal elb if not responce["NetworkInterfaces"]: responce = ec2.describe_network_interfaces( Filters=[{'Name':'private-ip-address','Values':[elb_listener_ip]}] ) availabilityzone='none' privateipaddress='none' if responce["NetworkInterfaces"]: if responce["NetworkInterfaces"][0]: if responce["NetworkInterfaces"][0]["AvailabilityZone"]: availabilityzone = responce["NetworkInterfaces"][0]["AvailabilityZone"] if responce["NetworkInterfaces"][0]["PrivateIpAddress"]: privateipaddress = responce["NetworkInterfaces"][0]["PrivateIpAddress"] print('AvailabilityZone: ' + availabilityzone) print('PrivateIpAddress: '+ privateipaddress)
実行結果
AvailabilityZone: ap-northeast-1a PrivateIpAddress: 172.31.xx.223
ALBのアクセスログのファイル名(キー)より、ノードのIPアドレスとアベイアビリティゾーンを確認できました。
まとめ
アクセスログに含まれないALBノードの情報をLambda(Python)を利用して取得できた事で、ログ項目に追加できる目処がつきました。
アベイアビリティゾーン別の解析以外にも、ALBを利用するシステムで問題が発生し、クライアントやターゲットのログとALBのログの突き合わせが必要となった場合、 ALBノードのIPアドレス情報が役立つと考えられます。
ログカラムを追加したアクセスログ、Kinesis Firehose などを利用してS3に保存する事で、Amazon Athena で解析する事が可能です。 アクセスログの追加したALBノードのIPアドレス、アベイアビリティゾーンに対応した解析の手順は追って紹介させて頂きたいと思います。
尚、今回紹介した方法では、ALBノードのスケールインや交換などがあった場合、削除されたノードのネットワークインターフェイス情報は取得できないため、 古いアクセスログでは結果が得られない可能性があります。
また、ALBのアクセスログファイル名に含まれる「_」、区切り文字の扱いについてはドキュメント上は明文化されておらず、今後変化する可能性もあります。 アベイアビリティゾーンやIPアドレス情報の欠落が許容できない用途には、今回の手法は適さない可能性がありますのでご留意ください。